package cinvestment;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Stack;

import flexibilityDesign.Support;
import support.Aggregate;
import support.AlgorithmSupport;
import util.Lang;
import util.MathUtil;
import util.NullOutputStream;
import ilog.concert.IloIntVar;
import ilog.concert.IloLPMatrix;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloRange;
import ilog.cplex.IloCplex;

public class Test {
	public static void main(String[] args) {
		double lb = -17606.60682156384;
		double ub = -17606.606821563622;
		double gap = ub - lb;
		System.out.println(gap + "; " + (gap > 0));
		gap = BigDecimal.valueOf(ub - lb).divide(BigDecimal.valueOf(-lb), 5, BigDecimal.ROUND_HALF_EVEN).doubleValue();
		System.out.println(gap + "; " + (0.0 >= 0.0000000000000000));

		// double[][] data = Support.readDemands(50, 4);
		// Random random = new Random();
		// for (int i = 0; i < 20; i++) {
		// for (int j = 0; j < 2; j++) {
		// data[i][j] = random.nextDouble();
		// }
		// }

		// Aggregate ag = new Aggregate();
		// Map<Integer, List<Double[]>> map = ag.kmeans(data, 10);
		// for (Map.Entry<Integer, List<Double[]>> entry : map.entrySet()) {
		// for (Double[] values : entry.getValue()) {
		// Lang.print(values);
		// }
		// System.out.println();
		// }

		// testsmall();
		// testOriginal();
		// checkModel();
		// mipTest();
		// testadding();
		// double[] re = testBalanced();
		// Lang.print(re);
		// testW();
		// testGivenCapacity();

		// testCplexProperty();
		// testCplex();

	}

	private static void testCplexProperty() {
		try {
			IloCplex cplex = new IloCplex();
			IloNumVar[] x = cplex.numVarArray(3, 0, 100);
			double[] c = { 2, 1.4, 3.9 };
			IloLinearNumExpr obj = cplex.linearNumExpr();
			obj.addTerms(c, x);
			cplex.addMinimize(obj);

			IloRange range1 = cplex.le(x[0], 5);
			cplex.addUserCut(range1);
			// cplex.addLe(x[0], 5);
			IloRange range = cplex.addLe(x[1], 4);
			cplex.exportModel("D:\\model1.lp");
			if (cplex.solve()) {
				System.out.println("objective=" + cplex.getObjValue());
				System.out.println(Arrays.toString(cplex.getValues(x)));
			}

			IloRange range2 = cplex.le(x[2], 2);
			cplex.addUserCut(range2);
			cplex.exportModel("D:\\model2.lp");
			if (cplex.solve()) {
				System.out.println("objective=" + cplex.getObjValue());
				System.out.println(Arrays.toString(cplex.getValues(x)));
			}

			cplex.end();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	private static void testGivenCapacity() {
		int N = 4;
		double[] deviations = { 0.3 };
		String structure = AlgorithmSupport.W_SHAPE;
		BigDecimal basic = BigDecimal.valueOf(N).divide(BigDecimal.valueOf(N - 1), 5, BigDecimal.ROUND_HALF_EVEN);
		for (int di = 0; di < deviations.length; di++) {
			try {
				Writer writer = new BufferedWriter(new FileWriter(Lang.projectRoot() + "\\data\\result\\record.txt",
						true));
				writer.write("%%%%%%%%% decrease side increase middle, dev = " + deviations[di] + " %%%%%%%% \r\n");
				for (int count = 0; count < 7; count++) {
					double[] caps = new double[N - 1];
					BigDecimal lamda = BigDecimal.valueOf(count + 1).multiply(BigDecimal.valueOf(0.2));
					// increase the middle one
					caps[0] = basic.subtract(lamda).doubleValue();
					if (caps[0] < 0)
						caps[0] = 0.0;
					caps[2] = caps[0];
					caps[1] = basic.add(BigDecimal.valueOf(2).multiply(basic.subtract(BigDecimal.valueOf(caps[0]))))
							.doubleValue();

					// // decrease the middle one
					// caps[1] = basic.subtract(lamda).doubleValue();
					// if (caps[1] < 0)
					// caps[1] = 0.0;
					// caps[0] = basic.add(
					// basic.subtract(BigDecimal.valueOf(caps[1])).divide(
					// BigDecimal.valueOf(2), 5,
					// BigDecimal.ROUND_HALF_EVEN)).doubleValue();
					// caps[2] = caps[0];

					// // control single--middle
					// caps[0] = basic.doubleValue();
					// caps[2] = caps[0];
					// caps[1] = basic.subtract(lamda).doubleValue();

					// // control single--side
					// caps[0] = basic.subtract(lamda).doubleValue();
					// caps[2] = basic.doubleValue();
					// caps[1] = basic.doubleValue();

					double[] penalties = new double[N];
					for (int i = 0; i < N; i++)
						penalties[i] = 1.0;
					Allocation solve = new Allocation(N, structure, 5000, penalties, caps, deviations[di]);
					double obj = solve.allocate();
					System.out.println(obj);

					writer.write(Arrays.toString(caps) + " : " + obj + "\r\n");
				}
				writer.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	private static void testW() {
		int[] Ns = { 4, 5, 6, 7 };
		for (int N : Ns) {
			double alpha = BigDecimal.valueOf(N).divide(BigDecimal.valueOf(N - 1), 5, BigDecimal.ROUND_HALF_EVEN)
					.doubleValue();

			double[] var = { 0.3, 0.5, 0.7, 0.9 };
			for (int vi = 0; vi < var.length; vi++) {
				double[] penalties = new double[N];
				for (int i = 0; i < N; i++)
					penalties[i] = 1.0;
				// CapacityInvestmentNoFixed cmc = new
				// CapacityInvestmentNoFixed(0.25,
				// N, 5000, penalties, 0.25);
				// CapacityInvestmentMultiCut cmc = new
				// CapacityInvestmentMultiCut(
				// 0.25, N, 5000, penalties, 0.25, 0.001);
				CapacityInvestmentGradient cmc = new CapacityInvestmentGradient(0.25, N, 5000, penalties, 0.25);
				cmc.setVarianceDemand(var[vi]);

				cmc.setStructure(AlgorithmSupport.W_SHAPE);
				List<Double[]> result = cmc.lshape();
				System.out.println("finished " + result.size());
				System.out.println(Arrays.toString(result.get(1)));
				try {
					Writer writer = new BufferedWriter(new FileWriter(
							Lang.projectRoot() + "\\data\\result\\record.txt", true));
					writer.write("std = " + var[vi] + ": " + Arrays.toString(result.get(1)) + "\r\n");
					writer.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private static void testadding() {
		try {
			IloCplex cplex = new IloCplex();
			IloNumVar[] x = cplex.numVarArray(2, 0.0, Double.MAX_VALUE);
			cplex.addMaximize(cplex.sum(cplex.prod(2.0, x[0]), cplex.prod(4.0, x[1])));
			cplex.addLe(x[0], 2);
			cplex.addLe(x[1], 3);
			if (cplex.solve()) {
				System.out.println("@check solve 1");
				System.out.println(cplex.getObjValue());
				System.out.println(Arrays.toString(cplex.getValues(x)));
			}
			cplex.exportModel("D://model1.lp");
			cplex.addLe(x[1], 2);
			if (cplex.solve()) {
				System.out.println("@check solve 2");
				System.out.println(cplex.getObjValue());
				System.out.println(Arrays.toString(cplex.getValues(x)));
			}
			cplex.exportModel("D://model2.lp");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void testCplex() {
		try {
			IloCplex cplex = new IloCplex();
			IloNumVar[] x = cplex.numVarArray(5, 0, Double.MAX_VALUE);
			cplex.addMaximize(cplex.sum(cplex.prod(4, x[0]), cplex.prod(-1, x[1])));
			cplex.addEq(cplex.sum(cplex.prod(7, x[0]), cplex.prod(-2, x[1]), x[2]), 14);
			cplex.addEq(cplex.sum(x[1], x[3]), 3);
			cplex.addEq(cplex.sum(cplex.prod(2, x[0]), cplex.prod(-2, x[1]), x[4]), 3);
			if (cplex.solve()) {
				System.out.println(Arrays.toString(cplex.getValues(x)));
			}
		} catch (Exception e) {
		}
	}

	private static void checkModel() {
		try {
			IloCplex cplex = new IloCplex();
			cplex.setOut(new NullOutputStream());
			cplex.importModel("D:\\workspace\\CapacityInvestment\\data\\master\\master2.lp");
			IloLPMatrix lp = (IloLPMatrix) cplex.LPMatrixIterator().next();
			if (cplex.solve()) {
				double[] x = cplex.getValues(lp);
				System.out.println("x vlaue: ");
				Lang.print(x);

				System.out.println("objective = " + cplex.getObjValue());
			}
			cplex.end();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void testOriginal() {
		double[][] demands = { { 0, 2 }, { 0, 1 }, { 1, 0 }, { 2, 0 }, { 1, 2 }, { 2, 1 }, { 0, 0 }, { 2, 2 }, { 1, 1 } };
		try {
			IloCplex cplex = new IloCplex();
			IloNumVar[] K = cplex.numVarArray(3, 0.0, Double.MAX_VALUE);
			IloNumVar[][] y = new IloNumVar[9][4];
			IloIntVar[] X = cplex.boolVarArray(3);
			for (int m = 0; m < 9; m++) {
				y[m] = cplex.numVarArray(4, 0.0, Double.MAX_VALUE);
			}
			double fixedCost = 0.0625;
			IloLinearNumExpr obj = cplex.linearNumExpr();
			double[] c1 = { 0.25, 0.25, 0.3125 };
			double p = 1.0;
			double[] c2 = new double[4];
			for (int i = 0; i < 4; i++) {
				c2[i] = BigDecimal.valueOf(-p).divide(BigDecimal.valueOf(9), 5, BigDecimal.ROUND_HALF_EVEN)
						.doubleValue();
			}

			obj.addTerms(c1, K);
			for (int m = 0; m < 9; m++) {
				obj.addTerms(c2, y[m]);
			}
			for (int j = 0; j < 3; j++)
				obj.addTerm(fixedCost, X[j]);
			cplex.addMinimize(obj);

			for (int m = 0; m < 9; m++) {
				cplex.addLe(cplex.sum(y[m][0], y[m][2]), demands[m][0]);
				cplex.addLe(cplex.sum(y[m][1], y[m][3]), demands[m][1]);
				cplex.addLe(y[m][0], K[0]);
				cplex.addLe(y[m][1], K[1]);
				cplex.addLe(cplex.sum(y[m][2], y[m][3]), K[2]);
			}
			for (int j = 0; j < 3; j++)
				cplex.addGe(cplex.prod(100, X[j]), K[j]);
			cplex.exportModel(System.getProperty("user.dir") + "\\model.lp");
			if (cplex.solve()) {
				System.out.println(cplex.getObjValue());
				System.out.println(Arrays.toString(cplex.getValues(K)));
				System.out.println(Arrays.toString(cplex.getValues(X)));
				for (int m = 0; m < 9; m++)
					System.out.println(Arrays.toString(cplex.getValues(y[m])));
			}
		} catch (Exception e) {

		}
	}

	private static void testsmall() {
		double[][] demands = { { 0, 2 }, { 0, 1 }, { 1, 0 }, { 2, 0 }, { 1, 2 }, { 2, 1 }, { 0, 0 }, { 2, 2 }, { 1, 1 } };
		// double[][] demands = AlgorithmSupport.getDemands(500, 2, 0.0, 2.0);
		int count = demands.length;

		for (int i = 0; i < count; i++) {
			System.out.println(Arrays.toString(demands[i]));
		}

		List<Double> etList = new ArrayList<>();
		List<Double[]> EtList = new ArrayList<>();

		double[] Kv = { 1, 1.0, 1.0 };

		System.out.println();

		double[] Et = new double[3];
		double et = 0.0;
		double Etk = 0.0;

		int iter = 1;
		while (true) {
			System.out.println("######## iter = " + iter);
			et = 0.0;
			Etk = 0.0;
			for (int j = 0; j < 3; j++)
				Et[j] = 0.0;
			try {
				double theta_value = -100;
				System.out.println("---------- master problem --------");
				if (iter != 1) {
					IloCplex master = new IloCplex();
					master.setOut(new NullOutputStream());
					IloLinearNumExpr obj = master.linearNumExpr();

					double[] flex = { 0.9, 0.9, 0.909 };
					IloNumVar[] K = master.numVarArray(3, 0.0, Double.MAX_VALUE);
					IloNumVar theta = master.numVar(-100, Double.MAX_VALUE);

					obj.addTerm(1.0, theta);
					obj.addTerms(flex, K);
					master.addMinimize(obj);

					for (int it = 0; it < etList.size(); it++) {
						master.addGe(master.sum(theta, master.scalProd(Lang.convertDouble2double(EtList.get(it)), K)),
								etList.get(it));
					}
					master.exportModel(Lang.projectRoot() + "\\data\\master\\master" + iter + ".lp");

					if (master.solve()) {
						Kv = master.getValues(K);
						theta_value = master.getValue(theta);

						// balance the symmetric variables
						if (etList.size() < (1 + K.length)) {
							if ((Kv[0] == 0 && Kv[1] != 0) || (Kv[0] != 0 && Kv[1] == 0)) {
								Kv[0] = BigDecimal.valueOf(Kv[1])
										.divide(BigDecimal.valueOf(2), 5, BigDecimal.ROUND_HALF_EVEN).doubleValue();
								Kv[1] = Kv[0];
							}
						}

						System.out.println("decision variables for iter(" + iter + ") = ");
					} else {
						System.out.println("unsolve status = " + master.getStatus());
					}
					System.out.println("objv = " + master.getObjValue());
					System.out.println("theta = " + master.getValue(theta));
					Lang.print(Kv);
					master.end();

					// if (iter == 3)
					// break;
				}

				System.out.println("---------- dual problem --------");
				// dual
				for (int m = 0; m < count; m++) {
					IloCplex cplex = new IloCplex();
					cplex.setOut(new NullOutputStream());
					IloNumVar[] pi = cplex.numVarArray(2, Double.NEGATIVE_INFINITY, 0.0);
					IloNumVar[] sigma = cplex.numVarArray(3, Double.NEGATIVE_INFINITY, 0.0);

					IloLinearNumExpr obj = cplex.linearNumExpr();
					obj.addTerms(demands[m], pi);
					obj.addTerms(Kv, sigma);
					cplex.addMaximize(obj);

					cplex.addLe(cplex.sum(pi[0], sigma[0]), -1.0);
					cplex.addLe(cplex.sum(pi[0], sigma[2]), -1.0);
					cplex.addLe(cplex.sum(pi[1], sigma[1]), -1.0);
					cplex.addLe(cplex.sum(pi[1], sigma[2]), -1.0);

					if (m == 0)
						cplex.exportModel(Lang.projectRoot() + "\\data\\dual\\dual" + iter + "_" + m + ".lp");

					if (cplex.solve()) {
						// System.out.println("******* " + m + " ******");
						double[] piv = cplex.getValues(pi);
						double[] siv = cplex.getValues(sigma);

						// Lang.print(piv);
						// Lang.print(siv);

						for (int i = 0; i < 2; i++)
							et += demands[m][i] * piv[i];

						for (int j = 0; j < 3; j++) {
							Etk += -Kv[j] * siv[j];
						}
						Et = MathUtil.sum(Et, siv);

						System.out.println("@ m(" + m + ") D=" + Arrays.toString(demands[m]) + "\t pi and sigma = "
								+ Arrays.toString(piv) + ",\t" + Arrays.toString(siv));
					} else {
						System.out.println("@ dual cannot solve : " + cplex.getStatus());
					}
					cplex.end();
				}
				et = BigDecimal.valueOf(et).divide(BigDecimal.valueOf(count), 5, BigDecimal.ROUND_HALF_EVEN)
						.doubleValue();
				Etk = BigDecimal.valueOf(Etk).divide(BigDecimal.valueOf(count), 5, BigDecimal.ROUND_HALF_EVEN)
						.doubleValue();

				for (int j = 0; j < 3; j++)
					Et[j] = BigDecimal.valueOf(-Et[j]).divide(BigDecimal.valueOf(count), 5, BigDecimal.ROUND_HALF_EVEN)
							.doubleValue();

				System.out.println("et = " + et);
				System.out.println("Etk = " + Etk);
				System.out.print("Et = ");
				Lang.print(Et);

				etList.add(et);
				EtList.add(Lang.convertdouble2Double(Et));

				double wt = et - Etk;
				double gap = wt - theta_value;
				System.out.println("gap = " + gap);
				if (theta_value > wt || iter > 199) {
					// System.out.println("final output. iter=" + iter +
					// ", gap="
					// + gap);
					Lang.print(Kv);
					break;
				}

				// System.out.println("gap for iter (" + iter + ") = " + gap);
				iter++;

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private static void test() {
		try {
			double[][] demands = { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 }, { 1, 2 }, { 2, 0 }, { 2, 1 },
					{ 2, 2 } };
			IloCplex cplex = new IloCplex();
			// double[][] demands = MathUtil.uniform(500, 2, 0.0, 2.0);
			// double[][] demands = { { 1, 2 }, { 2, 1 }, { 1, 0 }, { 0, 1 },
			// { 0, 0 } };
			// for (int i = 0; i < demands.length; i++) {
			// System.out.println(Arrays.toString(demands[i]));
			// }
			int count = demands.length;
			IloNumVar[] K = cplex.numVarArray(3, 0.0, Double.MAX_VALUE);
			IloNumVar[] X1 = cplex.numVarArray(count, 0.0, Double.MAX_VALUE);
			IloNumVar[] X2 = cplex.numVarArray(count, 0.0, Double.MAX_VALUE);
			IloNumVar[] X3 = cplex.numVarArray(count, 0.0, Double.MAX_VALUE);
			IloNumVar[] X4 = cplex.numVarArray(count, 0.0, Double.MAX_VALUE);

			double var = BigDecimal.valueOf(4).divide(BigDecimal.valueOf(count), 5, BigDecimal.ROUND_HALF_EVEN)
					.doubleValue();
			double[] vars = new double[count];
			for (int i = 0; i < count; i++)
				vars[i] = -var;
			IloLinearNumExpr obj = cplex.linearNumExpr();
			obj.addTerms(vars, X1);
			obj.addTerms(vars, X2);
			double[] fl = { 0.9, 0.9, 0.909 };
			obj.addTerms(fl, K);
			cplex.addMinimize(obj);

			for (int i = 0; i < count; i++) {
				cplex.addLe(cplex.sum(X1[i], X3[i]), demands[i][0]);
				cplex.addLe(cplex.sum(X2[i], X4[i]), demands[i][1]);

				cplex.addLe(X1[i], K[0]);
				cplex.addLe(X2[i], K[1]);
				cplex.addLe(cplex.sum(X3[i], X4[i]), K[2]);
			}

			cplex.exportModel(System.getProperty("user.dir") + "\\model.lp");
			if (cplex.solve()) {
				System.out.println(Arrays.toString(cplex.getValues(K)));
				System.out.println(cplex.getObjValue());
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static double[] testBalanced() {
		int N = 2;
		List<Plant> list = AlgorithmSupport.createChainingPlants(N);
		int cn = 1;
		// double[][] K = { { 1.398144414, 1.019203432, 1.391220404 },
		// { 1.26952275, 1.26952275, 1.26952275 },
		// { 1.398144414, 1.391220404, 1.019203432 },
		// { 1.019203432, 1.398144414, 1.391220404 } };
		double[][] K = { { 0.995161046, 0.995161046 } };
		int n = list.size();
		double penalty = 1.0;

		int sn = 5000;
		double[][] demands = AlgorithmSupport.getDemands_normal(sn, N, 1.0, 0.3, 0, 2.0);

		double[] results = new double[cn];

		for (int ins = 0; ins < cn; ins++) {
			double[] sr = new double[sn];
			for (int m = 0; m < sn; m++) {
				System.out.println("instance: " + m);
				try {
					IloCplex cplex = new IloCplex();
					cplex.setOut(new NullOutputStream());
					IloNumVar[][] x = new IloNumVar[N][n];
					for (int i = 0; i < N; i++)
						for (int j = 0; j < n; j++)
							x[i][j] = cplex.numVar(0.0, Double.MAX_VALUE);
					IloLinearNumExpr obj = cplex.linearNumExpr();
					for (int i = 0; i < N; i++) {
						for (int j = 0; j < n; j++) {
							if (list.get(j).isAvailable(i))
								obj.addTerm(-penalty, x[i][j]);
						}
					}
					cplex.addMinimize(obj);
					for (int i = 0; i < N; i++) {
						IloLinearNumExpr pro = cplex.linearNumExpr();
						for (int j = 0; j < n; j++) {
							if (list.get(j).isAvailable(i))
								pro.addTerm(1.0, x[i][j]);
						}
						cplex.addLe(pro, demands[m][i]);
					}
					for (int j = 0; j < n; j++) {
						IloLinearNumExpr cap = cplex.linearNumExpr();
						for (int i = 0; i < N; i++) {
							if (list.get(j).isAvailable(i))
								cap.addTerm(1.0, x[i][j]);
						}
						cplex.addLe(cap, K[ins][j]);
					}
					if (cplex.solve()) {
						double objv = cplex.getObjValue();
						sr[m] = objv;
					}
					cplex.end();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			results[ins] = MathUtil.average(sr);
		}
		return results;
	}
}
